1 module commons;
2 public import arsd.terminal : Color, ConsoleOutputType, ConsoleInputFlags;
3 public static import arsd.terminal;
4 public import std.array:join, split;
5 public import std.json;
6 public import std.path;
7 public import std.process;
8 public static import std.file;
9 public import default_handlers;
10 public import redub.api;
11 
12 
13 enum hipremeEngineRepo = "https://github.com/MrcSnm/HipremeEngine.git";
14 enum ConfigFile = "gamebuild.json";
15 
16 JSONValue engineConfig;
17 Config configs;
18 
19 string pathBeforeNewLdc;
20 
21 struct Terminal
22 {
23 	import std.stdio;
24 	import core.sync.mutex;
25 
26 	arsd.terminal.Terminal* arsdTerminal;
27 	Mutex mtx;
28 	this(arsd.terminal.Terminal* arsdTerminal)
29 	{
30 		this.arsdTerminal = arsdTerminal;
31 		mtx = new Mutex();
32 	}
33 
34 	void color(Color main, Color secondary)
35 	{
36 		if(arsdTerminal) synchronized(mtx)
37 			arsdTerminal.color(main, secondary);
38 	}
39 	int cursorY()
40 	{
41 		if(arsdTerminal) synchronized(mtx)
42 		{
43 			arsdTerminal.updateCursorPosition();
44 			return arsdTerminal.cursorY;
45 		}
46 		return 0;
47 	}
48 	string getline(string message)
49 	{
50 		if(arsdTerminal) synchronized(mtx) return arsdTerminal.getline(message); 
51 		std.stdio.writeln("Can't get line with message [", message, "]");
52 		return "";
53 	}
54 	void moveTo(int x, int y){if(arsdTerminal) synchronized(mtx) arsdTerminal.moveTo(x, y);}
55 	void clear(){if(arsdTerminal) synchronized(mtx) arsdTerminal.clear();}
56 	void write(T...)(T args)
57 	{
58 		if(arsdTerminal) synchronized(mtx) arsdTerminal.write(args);
59 		else std.stdio.write(args);
60 	}
61 	void flush()
62 	{
63 		if(arsdTerminal) synchronized(mtx)
64 		{
65 			arsdTerminal.flush();
66 			arsdTerminal.updateCursorPosition();
67 		}
68 	}
69 
70 	int wait(Pid pid)
71 	{
72 		this.flush;
73 		return std.process.wait(pid);
74 	}
75 
76 	void hideCursor(){ if(arsdTerminal) synchronized(mtx) arsdTerminal.hideCursor();}
77 	void showCursor(){ if(arsdTerminal) synchronized(mtx) arsdTerminal.showCursor();}
78 	void clearToEndOfLine()
79 	{
80 		if(arsdTerminal) synchronized(mtx) arsdTerminal.clearToEndOfLine();
81 		flush();
82 	}
83 	void clearLine()
84 	{
85 		moveTo(0, cursorY);
86 		clearToEndOfLine();
87 		flush();
88 	}
89 
90 	void writeln(T...)(T args)
91 	{
92 		if (arsdTerminal) synchronized(mtx) arsdTerminal.writeln(args);
93 		else std.stdio.writeln(args);
94 	}
95 	~this()
96 	{
97 		showCursor();
98 		if(arsdTerminal) synchronized(mtx) destroy(*arsdTerminal);
99 		mtx = null;
100 	}
101 }
102 
103 struct RealTimeConsoleInput
104 {
105 	private arsd.terminal.RealTimeConsoleInput* input;
106 	this(arsd.terminal.RealTimeConsoleInput* input){this.input = input;}
107 	dchar getch()
108 	{
109 		if(input) return input.getch();
110 		return '\0';
111 	}
112 	~this()
113 	{
114 		if(input) destroy(*input);
115 	}
116 }
117 
118 struct TerminalColors
119 {
120 	private Terminal* _t;
121 	this(Color main, Color secondary, ref Terminal terminal)
122 	{
123 		_t = &terminal;
124 		_t.color(main, secondary);
125 	}
126 	~this()
127 	{
128 		_t.color(Color.DEFAULT, Color.DEFAULT);
129 		_t.flush();
130 	}
131 }
132 
133 struct WorkingDir
134 {
135 	private string _currDir;
136 	this(string targetDir)
137 	{
138 		_currDir = std.file.getcwd();
139 		std.file.chdir(targetDir);
140 	}
141 	~this(){std.file.chdir(_currDir);}
142 }
143 
144 enum ChoiceResult
145 {
146 	None,
147 	Continue,
148 	Error,
149 	Back,
150 }
151 
152 struct Choice
153 {
154 	string name;
155 	ChoiceResult function(Choice* self, ref Terminal t, ref RealTimeConsoleInput input, in CompilationOptions opts) onSelected;
156 	bool shouldTime;
157 	string function() updateChoice;
158 	bool scriptOnly;
159 	bool disableSelectedConfigCache;
160 
161 
162 
163 
164 
165 
166 	this(string name,
167 	ChoiceResult function(Choice* self, ref Terminal t, ref RealTimeConsoleInput input, in CompilationOptions opts) onSelected,
168 	bool shouldTime = false,
169 	string function() updateChoice = null, bool scriptOnly = false, bool disableSelectedConfigCache = false)
170 	{
171 		this.name = updateChoice ? updateChoice() : name;
172 		this.onSelected = onSelected;
173 		this.shouldTime = shouldTime;
174 		this.updateChoice = updateChoice;
175 		this.scriptOnly = scriptOnly;
176 		this.disableSelectedConfigCache = disableSelectedConfigCache;
177 	}
178 
179 	bool opEquals(ref const Choice other) const 
180 	{
181 		return name == other.name;
182 	}
183 	bool opEquals(string choiceName) const
184 	{
185 		return name == choiceName;	
186 	}
187 }
188 
189 struct Config
190 {
191 	JSONValue cfg;
192 
193 	this(JSONValue js)
194 	{
195 		cfg = js;
196 		if(!("windows" in cfg)) cfg.object["windows"] = JSONValue(string[string].init);
197 		if(!("posix" in cfg)) cfg["posix"] = JSONValue(string[string].init);
198 	}
199 	string toString()
200 	{
201 		return cfg.toPrettyString(JSONOptions.doNotEscapeSlashes);
202 	}
203 
204 	auto opBinaryRight(string op, R)(const R rhs) const
205 	if(op == "in")
206 	{
207 		version(Windows){return rhs in cfg["windows"];}
208 		else version(Posix){return rhs in cfg["posix"];}
209 		else static assert(false, "OS not supported");
210 	}
211 
212 	ref auto opIndexAssign(T)(T value, string obj)
213 	{
214 		version(Windows){return cfg["windows"][obj] = value;}
215 		else version(Posix){return cfg["posix"][obj] = value;}
216 		else static assert(false, "OS not supported");
217 	}
218 
219 	ref auto opIndex(string obj)
220 	{
221 		version(Windows){return cfg["windows"][obj];}
222 		else version(Posix){return cfg["posix"][obj];}
223 		else static assert(false, "OS not supported");
224 	}
225 }
226 
227 struct CompilationOptions
228 {
229 	bool dubVerbose;
230 	bool force;
231 	bool tempBuild;
232 	string getDubOptions() const
233 	{
234 		string ret;
235 		if(force) ret~= " --force";
236 		if(tempBuild) ret~= " --temp-build";
237 		if(dubVerbose) ret~= " --verbose";
238 		return ret;
239 	}
240 }
241 
242 T[] unique(T)(T[] input)
243 {
244 	bool[T] seen;
245 	T[] ret;
246 	foreach(v; input)
247 	{
248 		if(!(v in seen))
249 		{
250 			seen[v] = true;
251 			ret~= v;
252 		}
253 	}
254 	return ret;
255 }
256 
257 size_t selectChoiceBase(ref Terminal terminal, ref RealTimeConsoleInput input, Choice[] choices, 
258 	string selectionTitle, size_t selectedChoice = 0)
259 {
260 	bool exit;
261 	enum ESC = 983067;
262 	enum ArrowUp = 983078;
263 	enum ArrowDown = 983080;
264 	enum SelectionHint = "Select an option by using W/S or Arrow Up/Down and choose it by pressing Enter.";
265 
266 	static bool isFirst = true;
267 	static void changeChoiceClear(ref Terminal t, Choice[] choices, string title, Choice current, Choice next, int nextCursorOffset, bool bClear)
268 	{
269 		t.color(Color.DEFAULT, Color.DEFAULT);
270 		if(bClear)
271 			t.clear();
272 		t.writelnHighlighted(title);
273 		t.writeln(SelectionHint);
274 		foreach(i, c; choices)
275 		{
276 			if(c.name == next.name) with(TerminalColors(Color.green, Color.DEFAULT, t))
277 				t.writeln(">> ", c.name);
278 			else t.writeln(c.name);
279 		}
280 		t.flush;
281 	}
282 
283 	if(!isFirst)
284 		terminal.clear();
285 	int startLine = terminal.cursorY;
286 	terminal.flush();
287 	terminal.moveTo(0, startLine + cast(int)selectedChoice);
288 	terminal.hideCursor();
289 
290 
291 	size_t oldChoice = selectedChoice;
292 	while(!exit)
293 	{
294 		changeChoiceClear(terminal, choices, selectionTitle, choices[oldChoice], choices[selectedChoice], cast(int)(cast(long)selectedChoice-oldChoice), !isFirst);
295 		isFirst = false;
296 		oldChoice = selectedChoice;
297 
298 		CheckInput:
299 		size_t choice = input.getch;
300 		switch(choice)
301 		{
302 			case 'w', 'W', ArrowUp:
303 				selectedChoice = (selectedChoice + choices.length - 1) % choices.length;
304 				break;
305 			case 's', 'S', ArrowDown:
306 				selectedChoice = (selectedChoice+1) % choices.length;
307 				break;
308 			case ESC:
309 				selectedChoice = choices.length - 1;
310 				exit = true;
311 				break;
312 			case '\n':
313 				exit = true;
314 				break;
315 			default:
316 				goto CheckInput;
317 		}
318 	}
319 	import std.algorithm.searching;
320 	terminal.moveTo(0, cast(int)startLine);
321 	//Title + SelectionHint
322 	foreach(i; 0..choices.length+ ( count(selectionTitle, "\n")+2))
323 		terminal.moveTo(0, cast(int)(startLine+i)), terminal.clearToEndOfLine();
324 	terminal.moveTo(0, cast(int)startLine+1); //Jump title
325 	terminal.writelnSuccess(">> ", choices[selectedChoice].name);
326 
327 	terminal.showCursor();
328 	return selectedChoice;
329 }
330 
331 string[] getProjectsAvailable()
332 {
333 	import std.array;
334 	import std.algorithm;
335 	if(!("projectsAvailable" in configs))
336 		return [];
337 
338 	string[] existing = array(configs["projectsAvailable"].array.map!(v => v.str).filter!(v => std.file.exists(v)));
339 	if(existing.length != configs["projectsAvailable"].array.length)
340 	{
341 		configs["projectsAvailable"] = JSONValue(existing);
342 		updateConfigFile();
343 	}
344 	return existing;
345 }
346 
347 string getValidPath(ref Terminal t, string pathRequired)
348 {
349 	string path;
350 	while(true)
351 	{
352 		path = t.getline(pathRequired);
353 		if(std.file.exists(path))
354 			return path;
355 	}
356 }
357 
358 bool filesExists(string basePath, scope immutable string[] files...)
359 {
360 	foreach(f; files)
361 	{
362 		auto temp = buildNormalizedPath(basePath, f);
363 		if(!std.file.exists(temp)) return false;
364 	}
365 	return true;
366 }
367 
368 string getFirstExisting(string basePath, scope string[] tests...)
369 {
370 	foreach(t; tests)
371 	{
372 		auto temp = buildNormalizedPath(basePath, t);
373 		if(std.file.exists(temp)) return temp;
374 	}
375 	return "";
376 }
377 
378 string getHipPath(scope string[] paths...)
379 {
380 	return buildPath([configs["hipremeEnginePath"].str] ~ paths);
381 }
382 
383 string getFirstExistingVar(scope string[] vars...)
384 {
385 	foreach(variable; vars)
386 	{
387 		if(variable in environment)
388 			return environment[variable];
389 	}
390 	return "";
391 }
392 
393 
394 
395 bool hasLdc()
396 {
397 	return ("ldcPath" in configs) !is null;
398 }
399 
400 bool dbgExecuteShell(scope const(char)[] command, ref Terminal t, const string[string] env = null)
401 {
402 	t.writeln("Executing command: ", command);
403 	auto ret = executeShell(command, env);
404 	if(ret.status)
405 	{
406 		t.writelnError(cast(string)("Command '"~command~"' failed with: "~ ret.output));
407 		t.flush;
408 	}
409 	return ret.status == 0;
410 }
411 
412 string findProgramPath(string program)
413 {
414 	import std.algorithm:countUntil;
415 	import std.process;
416 	string searcher;
417 	version(Windows) searcher = "where";
418 	else version(Posix) searcher = "which";
419 	else static assert(false, "No searcher program found in this OS.");
420 	auto shellRes = executeShell(searcher ~" " ~ program,
421 	[
422 		"PATH": environment["PATH"]
423 	]);
424     if(shellRes.status == 0)
425 		return shellRes.output[0..shellRes.output.countUntil("\n")];
426    	return null;
427 }
428 
429 void writelnHighlighted(ref Terminal t, scope string[] what...)
430 {
431 	with(TerminalColors(Color.yellow, Color.DEFAULT, t))
432 		t.writeln(what.join());
433 }
434 
435 void writelnSuccess(ref Terminal t, scope string[] what...)
436 {
437 	with(TerminalColors(Color.green, Color.DEFAULT, t))
438 		t.writeln(what.join());
439 }
440 
441 void writelnError(ref Terminal t, scope string[] what...)
442 {
443 	with(TerminalColors(Color.red, Color.DEFAULT, t))
444 		t.writeln(what.join());
445 }
446 
447 auto timed(T)(ref Terminal t, scope lazy T val){return timed(t, "", val);}
448 auto timed(T)(ref Terminal t, string measuringWhat, scope lazy T val)
449 {
450 	import std.datetime.stopwatch;
451 	StopWatch sw = StopWatch(AutoStart.yes);
452 	static if(is(T == void))
453 	{
454 		val;
455 		t.writeln(measuringWhat, sw.peek.total!"msecs", "ms");
456 	}
457 	else 
458 	{
459 		auto ret = val;
460 		t.writeln(measuringWhat, sw.peek.total!"msecs", "ms");
461 		return ret;
462 	}
463 }
464 
465 
466 
467 struct Session
468 {
469 	struct Cache
470 	{
471 		size_t line;
472 		string file;
473 	}
474 	bool[Cache] cache;
475 }
476 private __gshared Session session;
477 
478 void cached(scope void delegate() dg, string f = __FILE__, size_t l = __LINE__)
479 {
480 	if(!(Session.Cache(l, f) in session.cache))
481 	{
482 		session.cache[Session.Cache(l, f)] = true;
483 		dg();
484 	}
485 }
486 
487 /** 
488  * Clears all cache.
489  * This may be useful after a dub.template.json was already generated.
490  * Or for example, after changing the current game.
491  */
492 void clearCache()
493 {
494 	session.cache.clear;
495 }
496 
497 bool pollForExecutionPermission(ref Terminal t, ref RealTimeConsoleInput input, string operation)
498 {
499 	t.writelnHighlighted(operation~" [Y]es/[N]o");
500 	t.flush;
501 	while(true)
502 	{
503 		switch(input.getch)
504 		{
505 			case 'y', 'Y': return true;
506 			case 'n', 'N': return false;
507 			default: break;
508 		}
509 	}
510 }
511 
512 bool extractZipToFolder(string zipPath, string outputDirectory, ref Terminal t)
513 {
514 	import std.zip;
515 	ZipArchive zip = new ZipArchive(std.file.read(zipPath));
516 	if(!std.file.exists(outputDirectory))
517 	{
518 		t.writeln("Creating directory ", outputDirectory);
519 		t.flush;
520 		std.file.mkdirRecurse(outputDirectory);
521 	}
522 	foreach(fileName, archiveMember; zip.directory)
523 	{
524 		string outputFile = buildNormalizedPath(outputDirectory, fileName);
525 		if(!std.file.exists(outputFile))
526 		{
527 			if(archiveMember.expandedSize == 0)
528 				std.file.mkdirRecurse(outputFile);
529 			else
530 			{
531 				string currentDirName = outputFile;
532 				///For some reason on linux it thinks that .a files are directories
533 				t.writeln("Extracting ", fileName);
534 				t.flush;
535 				currentDirName = currentDirName.dirName;
536 				if(!std.file.exists(currentDirName))
537 					std.file.mkdirRecurse(currentDirName);
538 				std.file.write(outputFile, zip.expand(archiveMember));
539 			}
540 		}
541 	}
542 	return true;
543 }
544 
545 
546 bool isCompactedFile(string fileName)
547 {
548 	import std.path;
549 	switch(fileName.extension)
550 	{
551 		case ".gz", ".xz", ".zip", ".7zip", ".7z": return true;
552 		default: return false;
553 	}
554 }
555 
556 
557 bool extractToFolder(string zPath, string outputDirectory, ref Terminal t, ref RealTimeConsoleInput input)
558 {
559 	import features._7zip;
560 	import std.path;
561 	switch(zPath.extension)
562 	{
563 		case ".gz", ".xz":
564 			version(Posix)
565 			{
566 				return extractTarGzToFolder(zPath, outputDirectory, t);
567 			}
568 			else version(Windows)
569 			{
570 				///Handles .gz, .xz
571 				if(!extract7ZipToFolder.execute(t, input, zPath, dirName(zPath)))
572 					return false;
573 				t.writelnSuccess("Extracted to ", dirName(zPath));
574 				//Now handle tar
575 				return extractTarToFolder(t, zPath[0..$-".xz".length], outputDirectory);
576 			}
577 			else assert(false, "No .tar.gz support on non Posix");
578 		case ".zip":
579 			return extractZipToFolder(zPath, outputDirectory, t);
580 		case ".7zip", ".7z":
581 			return extract7ZipToFolder.execute(t, input, zPath, outputDirectory);
582 		default:
583 			t.writelnError("Could not detect compressed archive type for "~zPath);
584 			return false;
585 	}
586 }
587 
588 string executableExtension(string path)
589 {
590 	version(Windows) return path~".exe";
591 	return path;
592 }
593 
594 version(Posix)
595 bool extractTarGzToFolder(string tarGzPath, string outputDirectory, ref Terminal t)
596 {
597 	if(!std.file.exists(tarGzPath))
598 	{
599 		t.writelnError("File ", tarGzPath, " does not exists.");
600 		return false;
601 	}
602 	t.writeln("Extracting ", tarGzPath, " to ", outputDirectory);
603 	t.flush;
604 	std.file.mkdirRecurse(outputDirectory);
605 	return dbgExecuteShell("tar -xf "~tarGzPath~" -C "~outputDirectory, t);
606 }
607 
608 bool extractTarToFolder(ref Terminal t, string tarPath, string outputDirectory)
609 {
610 	import archive.tar;
611 	try
612 	{
613 		auto tar = new TarArchive(std.file.read(tarPath));
614 		t.writeln("Extracting ", tarPath, " to ", outputDirectory);
615 		foreach(file; tar.files)
616 		{
617 			if(file.path == "@LongLink")
618 				continue;
619 			string outputPath = buildNormalizedPath(outputDirectory, file.path);
620 			if(std.file.exists(outputPath))
621 				continue;
622 			string targetDir = dirName(outputPath);
623 			if(!std.file.exists(targetDir))
624 				t.writeln("\t->", targetDir);
625 			std.file.mkdirRecurse(targetDir);
626 			std.file.write(outputPath, file.data);
627 		}
628 	}
629 	catch(Exception e)
630 	{
631 		t.writelnError("File ", tarPath, " does not exists, ", e.toString);
632 		return false;
633 	}
634 	return true;
635 }
636 
637 bool isRecognizedExtension(string ext)
638 {
639 	switch(ext)
640 	{
641 		case ".7z", ".7zip", ".tar", ".xz", ".zf", ".bz", ".gz", ".zip": return true;
642 		default: return false;
643 	}
644 }
645 
646 /** 
647  * Removes the extension (while keeping numeric extensions such as dmd-2.105.0)
648  * Params:
649  *   input = Input to remove extension
650  * Returns: 
651  */
652 string removeExtension(string input)
653 {
654 	import std.string:isNumeric;
655 	string ext;
656 	while((ext = input.extension).length && ext.isRecognizedExtension)
657 		input = input.setExtension("");
658 	return input;
659 }
660 
661 /** 
662  * 
663  * Params:
664  *   purpose = A message for the user to understand what is happening
665  *   link = The link to file which will be downloaded to a temp dir
666  *   outputName = A file name with a compressed archive extension (e.g: .zip, .7z, .tar.xz)
667  *   outputDirectory = Where the file from outputName will be extracted
668  *   t = Terminal 
669  *   input = RealTimeInput
670  * Returns: 
671  */
672 bool installFileTo(string purpose, string link, string outputName,
673 string outputDirectory, ref Terminal t, ref RealTimeConsoleInput input)
674 {
675 	string downloadDir = buildNormalizedPath(std.file.tempDir, outputName);
676 	if(!downloadFileIfNotExists(purpose, link, downloadDir, t, input))
677 	{
678 		t.writelnError("Download failed");
679 		t.flush;
680 		return false;
681 	}
682 
683 
684 	outputName = outputName.removeExtension;
685 
686 	string installDir = buildNormalizedPath(outputDirectory, outputName);
687 	if(!extractToFolder(downloadDir, installDir, t, input))
688 	{
689 		t.writelnError("Could not extract ",downloadDir, " to ", installDir);
690 		return false;
691 	}
692 
693 	return true;
694 }
695 
696 bool makeFileExecutable(string filePath)
697 {
698 	version(Windows) return true;
699 	version(Posix)
700 	{
701 		if(!std.file.exists(filePath)) return false;
702 		import std.conv:octal;
703 		std.file.setAttributes(filePath, octal!700);
704 		return true;
705 	}
706 }
707 
708 bool downloadFileIfNotExists(
709 	string purpose, string link, string outputName,
710 	ref Terminal t, ref RealTimeConsoleInput input
711 )
712 {
713 	import std.net.curl;
714 	import std.conv:to;
715 	string theDir = dirName(outputName);
716 	if(!std.file.exists(theDir))
717 		std.file.mkdirRecurse(theDir);
718 	if(!std.file.exists(outputName))
719 	{
720 		if(!pollForExecutionPermission(t, input, "Your system will download a file: "~ purpose~"("~link~")"))
721 			return false;
722 		t.writelnHighlighted("Download started.");
723 		t.flush;
724 		size_t time = downloadWithProgressBar(t, link, outputName);
725 		t.writelnSuccess("\nDownload succeeded after ", time.to!string, " msecs!");
726 		t.flush;
727 	}
728 	return true;
729 }
730 
731 private void terminalProgressBar(ref Terminal t, float percentage, ubyte ticksCount = 32)
732 {
733 	assert(percentage <= 1.0 && percentage >= 0, "Invalid percentage.");
734 
735 	ubyte drawnTicks = cast(ubyte)(ticksCount*percentage);
736 	int line = t.cursorY;
737 	t.moveTo(0, line);
738 	t.clearToEndOfLine();
739 	t.write("<");
740 	foreach(int i; 0..ticksCount)
741 	{
742 		t.color(i < drawnTicks ? Color.green : Color.red, Color.DEFAULT);
743 		t.write(i < drawnTicks ? "=" : ".");
744 	}
745 	t.color(Color.DEFAULT, Color.DEFAULT);
746 	t.write("> (", percentage*100, "%)");
747 	t.flush();
748 }
749 
750 ///Adds a path to PATH
751 void addToPath(string pathToAdd)
752 {
753 	import std.array:join;
754 	string concatPath = ":";
755     version(Windows) concatPath = ";";
756     environment["PATH"] = join([
757 		pathToAdd,
758         environment["PATH"]
759     ], concatPath);
760 }
761 
762 size_t downloadWithProgress(string url, string saveToPath, void delegate(float t) onProgress, size_t updateDelay = 125)
763 {
764 	import std.net.curl:HTTP;
765 	import core.time:dur;
766 	import std.datetime.stopwatch:StopWatch, AutoStart;
767 	import std.stdio : File;
768 	size_t received, contentLength;
769 	HTTP conn = HTTP();
770 	conn.url = url;
771 
772 	string targetDir = dirName(saveToPath);
773 	if(!std.file.exists(targetDir))
774 		std.file.mkdirRecurse(targetDir);
775 
776 	static void writer(string path)
777 	{
778 		auto f = File(path, "wb");
779 		while(true)
780 		{
781 			immutable(ubyte)[] data = receiveOnly!(immutable(ubyte)[]);
782 			if(data.length == 0)
783 				break;
784 			f.rawWrite(data);
785 		}
786 		ownerTid.send(true);
787 	}
788 	auto writerTid = spawn(&writer, saveToPath);
789 	StopWatch updateDelayChecker = StopWatch(AutoStart.yes);
790 	size_t downloadTime;
791 	conn.onReceive = (ubyte[] data)
792 	{
793 		import std.conv:to;
794 		import std.stdio;
795 		if(contentLength == 0)
796 			contentLength = conn.responseHeaders["content-length"].to!size_t;
797 		received+= data.length;
798 		if(updateDelayChecker.peek.total!"msecs" >= updateDelay || received == contentLength)
799 		{
800 			downloadTime+= updateDelayChecker.peek.total!"msecs";
801 			onProgress(cast(float)received/contentLength);
802 			updateDelayChecker.reset();
803 		}
804 		send(writerTid, data.idup);
805 		return data.length;
806 	};
807 	conn.perform();
808 	send(writerTid, (immutable(ubyte)[]).init);
809 	receiveTimeout(dur!"msecs"(1000), (bool){}); //Block until finish
810 	return downloadTime; 
811 }
812 
813 /**
814 *	Same as std.net.curl.download
815 *	Difference is that it shows a progress bar while downloading.
816 *	Returns the time needed to download.
817 */
818 size_t downloadWithProgressBar(ref Terminal t, string url, string saveToPath, size_t updateDelay = 125)
819 {
820 	t.hideCursor();
821 	scope(exit)
822 	{
823 		t.showCursor();
824 		t.writeln("");
825 	}
826 	return downloadWithProgress(url, saveToPath, (float progress)
827 	{
828 		terminalProgressBar(t, progress);
829 	});
830 }
831 
832 
833 private string getConfigPath()
834 {
835 	import core.runtime;
836 	static string cfgPath;
837 	if(cfgPath == "")
838 		cfgPath = buildNormalizedPath(Runtime.args[0].dirName, ConfigFile);
839 	return cfgPath;
840 }
841 private string getEngineConfigPath()
842 {
843 	return getHipPath("bin" ,"desktop", "engine_opts.json");
844 }
845 void updateEngineFile()
846 {
847 	std.file.write(getEngineConfigPath, engineConfig.toPrettyString());
848 }
849 void updateConfigFile()
850 {
851 	if(!("defaultProject" in engineConfig) && "gamePath" in configs)
852 	{
853 		engineConfig["defaultProject"] = configs["gamePath"].str;
854 		updateEngineFile();
855 	}
856 	std.file.write(getConfigPath, configs.toString());
857 }
858 string getSourceCodeEditor(string projectPath)
859 {
860 	if(!("sourceCodeEditor" in configs) || configs["sourceCodeEditor"].str.length == 0)
861 	{
862 		string out_Editor;
863 		if(getDefaultSourceEditor(buildNormalizedPath(projectPath, "source", "gamescript", "entry.d"), out_Editor))
864 			configs["sourceCodeEditor"] = out_Editor;
865 		else
866 			configs["sourceCodeEditor"] = "";
867 		updateConfigFile();
868 	}
869 
870 	return configs["sourceCodeEditor"].str;
871 }
872 
873 bool openSourceCodeEditor(string projectPath)
874 {
875 	string sourceEditor = getSourceCodeEditor(projectPath);
876 	if(!sourceEditor.length)
877 		return false;
878 
879 	spawnShell(sourceEditor.escapeShellCommand~" "~projectPath.escapeShellCommand);
880 	return true;
881 }
882 
883 
884 string getGitExec()
885 {
886 	if("git" in configs)
887 	{
888 		version(Windows) return buildNormalizedPath(configs["git"].str, "git.exe");
889 		else return buildNormalizedPath(configs["git"].str, "git");
890 	}
891 	return "git ";
892 }
893 
894 
895 
896 private ChoiceResult _backFn(Choice* c, ref Terminal t, ref RealTimeConsoleInput input, in CompilationOptions cOpts)
897 {
898 	return ChoiceResult.Back;
899 }
900 Choice getBackChoice()
901 {
902 	return Choice("Back", &_backFn, false, null, false, true);
903 }
904 
905 
906 string getDubPath()
907 {
908 	string dub = buildNormalizedPath(configs["dubPath"].str, "dub");
909 	version(Windows) dub = dub.setExtension("exe");
910 	return dub;
911 }
912 
913 bool writeTemplate(ref Terminal t, string projectPath, string enginePath)
914 {
915 	import std.conv:to;
916 	import template_processor;
917 	string out_DubFile;
918 	auto res = processTemplate(projectPath, enginePath, out_DubFile);
919 	if(res != TemplateProcessorResult.success)
920 	{
921 		t.writelnError(res.to!string, ":", out_DubFile);
922 		return false;
923 	}
924 	try std.file.write(buildNormalizedPath(projectPath, "dub.json"), out_DubFile);
925 	catch(Exception e){
926 		t.writelnError("Could not write dub.json");
927 		return false;
928 	}
929 	return true;
930 }
931 
932 private int execDubBase(ref Terminal t, in DubArguments dArgs)
933 {
934 	if(absolutePath(configs["hipremeEnginePath"].str) != absolutePath(std.file.getcwd()))
935 	if(std.file.exists("dub.template.json"))
936 		return writeTemplate(t, std.file.getcwd(), configs["hipremeEnginePath"].str) ? 0 : -1;
937 	return 0;
938 }
939 
940 
941 mixin template BuilderPattern(Struct)
942 {
943 	static foreach(mem; __traits(allMembers, Struct))
944 	{
945 		import std.traits:isFunction;
946 		static if(!isFunction!(__traits(getMember, Struct, mem)) && mem[0] == '_')
947 		{
948 			mixin(typeof(__traits(getMember, Struct, mem)), " ", mem[1..$], "() { return ", mem, ";}",
949 			Struct, " ", mem[1..$], "(", typeof(__traits(getMember, Struct, mem)), " arg )",
950 			"{this.",mem, " = arg; return this;}");
951 		}
952 	}
953 }
954 
955 enum Compilers
956 {
957 	automatic = 0,
958 	ldc2,
959 	dmd
960 }
961 
962 
963 immutable string[] compilers = ["auto", "ldc2", "dmd"];
964 string getSelectedCompiler()
965 {
966 	const(JSONValue)* c = "selectedCompiler" in configs;
967 	if(!c) return "auto";
968 	return compilers[c.get!uint];
969 }
970 
971 
972 struct DubArguments
973 {
974 	string _command;
975 	string _configuration;
976 	CompilationOptions _opts;
977 	string _dir;
978 	string _preCommands;
979 	string _compiler = "auto";
980 	string _arch;
981 	string _build;
982 	string _recipe;
983 	string _runArgs;
984 	bool _confirmKey;
985 	bool _deep;
986 	bool _parallel = true;
987 
988 	mixin BuilderPattern!(DubArguments);
989 
990 	string getCompiler()
991 	{
992 		string c = _compiler;
993 		if(_compiler == "auto")
994 		{
995 			c = getSelectedCompiler();
996 		}
997 		if(c == "auto")
998 		{
999 			if(_arch)
1000 				c = "ldc2";
1001 			else
1002 				return "";
1003 		}
1004 		if(c == "ldc2")
1005 			c = buildNormalizedPath(configs["ldcPath"].str, "bin", "ldc2".executableExtension);
1006 		else if(c == "dmd")
1007 			c = buildNormalizedPath(configs["dmdPath"].str, "dmd".executableExtension);
1008 		return c;
1009 	}
1010 	
1011 	string getDubRunCommand()
1012 	{
1013 		string dub = getDubPath();
1014 		string a = command; ///Arguments
1015 		compiler = getCompiler();
1016 		if(parallel)      a~= " --parallel";
1017 		if(recipe)        a~= " --recipe="~recipe;
1018 		if(build)         a~= " --build="~build;
1019 		if(arch)          a~= " --arch="~arch;
1020 		if(compiler != "")a~= " --compiler="~compiler;
1021 		if(deep)		  a~= " --deep";
1022 		if(configuration) a~= " -c "~configuration;
1023 		if(opts != CompilationOptions.init) a~= opts.getDubOptions();
1024 		if(runArgs)       a~= " -- "~runArgs;
1025 
1026 
1027 		version(Windows)
1028 		{
1029 			if(confirmKey) a~= " && pause";
1030 		}
1031 		else version(Posix)
1032 		{
1033 			if(confirmKey) a~= " && read -p \"Press any key to continue... \" -n1 -s";
1034 		}
1035 		return preCommands~dub~" "~a;
1036 	}
1037 }
1038 
1039 int waitRedub(ref Terminal t, DubArguments dArgs, out ProjectDetails proj, string copyLinkerFilesTo = null)
1040 {
1041 	import redub.logging;
1042 	import redub.buildapi;
1043 	import redub.api;
1044 	if(execDubBase(t, dArgs) == -1) return -1;
1045 
1046 	setLogLevel(dArgs._opts.dubVerbose ? LogLevel.verbose : LogLevel.info);
1047 	proj = resolveDependencies(
1048 		dArgs._opts.force,
1049 		os,
1050 		CompilationDetails(dArgs.getCompiler(), null, dArgs._arch),
1051 		ProjectToParse(dArgs._configuration, std.file.getcwd(), null, dArgs._recipe),
1052 		InitialDubVariables.init,
1053 		BuildType.profile_gc
1054 	);
1055 	try {
1056 		if(buildProject(proj).error) return 1;
1057 	}
1058 	catch(BuildException err) { return 1; }
1059 	if(copyLinkerFilesTo.length)
1060 	{
1061 		import tools.copylinkerfiles;
1062 		string[] linkerFiles;
1063 		t.flush;
1064 		timed(t, "Copying Linker Files ",
1065 		{
1066 			proj.getLinkerFiles(linkerFiles);
1067 			copyLinkerFiles(linkerFiles, copyLinkerFilesTo);
1068 		}());
1069 		t.flush;
1070 	}
1071 	return 0;
1072 }
1073 
1074 void inParallel(scope void delegate()[] args...)
1075 {
1076 	import std.parallelism;
1077 
1078 	// version(AArch64)
1079 	// {
1080 	// 	foreach(action; args)
1081 	// 		action();
1082 	// }
1083 	// else
1084 	// {
1085 		foreach(action; parallel(args))
1086 			action();
1087 	// }
1088 }
1089 
1090 int waitDub(ref Terminal t, DubArguments dArgs, string copyLinkerFilesTo = null)
1091 {
1092 	///Detects the presence of a template file before executing.
1093 	ProjectDetails d;
1094 	if(dArgs._command.length >= 3 && dArgs._command[0..3] != "run") return waitRedub(t, dArgs, d, copyLinkerFilesTo);
1095 	if(execDubBase(t, dArgs) == -1) return -1;
1096 	string toExec = dArgs.getDubRunCommand();
1097 	t.writeln(toExec);
1098 	t.flush;
1099 	return t.wait(spawnShell(toExec));
1100 }
1101 
1102 int waitDubTarget(ref Terminal t, string target, DubArguments dArgs, string copyLinkerFilesTo = null)
1103 {
1104 	return waitDub(t, dArgs.recipe(buildPath(getBuildTarget(target), "dub.json")), copyLinkerFilesTo);
1105 }
1106 
1107 int waitAndPrint(ref Terminal t, Pid pid)
1108 {
1109 	return wait(pid);
1110 }
1111 
1112 public import std.concurrency;
1113 bool waitOperations(immutable bool delegate()[] operations)
1114 {
1115 	foreach(op; operations)
1116 	{
1117 		spawn((bool delegate() targetOperation)
1118 		{
1119 			ownerTid.send(targetOperation());
1120 		}, op);
1121 	}
1122 
1123 	foreach(i; 0..operations.length)
1124 		if(!receiveOnly!bool) 
1125 			return false;
1126 	return true;
1127 }
1128 
1129 
1130 void putResourcesIn(ref Terminal t, string where)
1131 {
1132 	import tools.copyresources;
1133 	string gPath = configs["gamePath"].str;
1134 	if(!isAbsolute(gPath))
1135 		gPath = absolutePath(gPath);
1136 	string resources = buildNormalizedPath(gPath, "assets");
1137 	if(!std.file.exists(resources))
1138 		std.file.mkdirRecurse(resources);
1139 	copyResources(t, resources, where, false);
1140 }
1141 
1142 void executeGameRelease(ref Terminal t)
1143 {
1144 	import tools.releasegame;
1145 	releaseGame(t, configs["gamePath"].str, getHipPath("build", "release_game"), false);
1146 }
1147 
1148 
1149 
1150 string selectInFolder(string selectWhat, string directory, ref Terminal t, ref RealTimeConsoleInput input, 
1151 scope string[] extFilters = [".DS_Store"])
1152 {
1153 	import std.string;
1154 	Choice[] choices;
1155 	LISTING_FILE: foreach(std.file.DirEntry e; std.file.dirEntries(directory, std.file.SpanMode.shallow))
1156 	{
1157 		foreach(f; extFilters)
1158 			if(e.name.endsWith(f)) continue LISTING_FILE;
1159 		choices~= Choice(e.name, null);
1160 	}
1161 	size_t choice;
1162 	choice = selectChoiceBase(t, input, choices, selectWhat);
1163 
1164 	return choices[choice].name;
1165 }
1166 
1167 /** 
1168  * Main difference from selectInFolder is that it returns the choice and also acacepts extra choices.
1169  * Params:
1170  *   selectWhat = Description
1171  *   directory = Directory to iterate
1172  *   t = 
1173  *   input = 
1174  *   extraChoices = May be used to go back or cancel process
1175  * Returns: Selected choice
1176  */
1177 Choice* selectInFolderExtra(string selectWhat, string directory, ref Terminal t, ref RealTimeConsoleInput input,
1178 return scope Choice[] choices, scope Choice[] extraChoices, scope string[] extFilters = [".DS_Store"])
1179 {
1180 	import std.string;
1181 	LISTING_FILES: 
1182 	foreach(std.file.DirEntry e; std.file.dirEntries(directory, std.file.SpanMode.shallow))
1183 	{
1184 		foreach(f; extFilters) if(e.name.endsWith(f)) continue LISTING_FILES;
1185 		choices~= Choice(e.name, null);
1186 	}
1187 	choices = (choices ~ extraChoices).unique;
1188 	size_t choice;
1189 	choice = selectChoiceBase(t, input, choices, selectWhat);
1190 
1191 	return &choices[choice];
1192 }
1193 
1194 
1195 
1196 version(Windows)
1197 {
1198 	import std.windows.registry;
1199 	Key windowsGetKeyWithPath(string[] path...)
1200 	{
1201 		Key hklm = Registry.localMachine;
1202 		if(hklm is null) throw new Error("No HKEY_LOCAL_MACHINE in this system.");
1203 		Key currKey = hklm;
1204 		foreach(p; path)
1205 		{
1206 			try{
1207 				currKey = currKey.getKey(p);
1208 				if(currKey is null) return null;
1209 			}
1210 			catch(Exception e)
1211 			{
1212 				return null;
1213 			}
1214 		}
1215 		return currKey;
1216 	}
1217 }
1218 
1219 string getBuildTarget(string target = __MODULE__)
1220 {
1221 	import std.string:split;
1222 	import std.exception:enforce;
1223 	target = target.split(".")[$-1];
1224 	string path = getHipPath("tools", "internal", "targets");
1225 	enforce(std.file.exists(path = buildPath(path, target)), "Target "~target~" does not exists.");
1226 	return path;
1227 }
1228 
1229 void outputTemplate(ref Terminal t, string templatePath)
1230 {
1231 	import template_processor;
1232 	string out_templ;
1233 	
1234 	switch(processTemplate(templatePath, configs["hipremeEnginePath"].str, out_templ, [
1235 		"TARGET_PROJECT": configs["gamePath"].str
1236 	]))
1237 	{
1238 		case TemplateProcessorResult.invalid:
1239 			t.writelnError("Could not process template from path ",templatePath);
1240 			throw new Error("Can't build with invalid template.");
1241 		case TemplateProcessorResult.notFound:
1242 			t.writelnHighlighted("Template at ", templatePath, " not found, your game may use dub.json instead.");
1243 			break;
1244 		default: case TemplateProcessorResult.success:
1245 			t.writelnSuccess("Template at path ", templatePath, " successfully generated");
1246 			std.file.write(buildPath(templatePath, "dub.json"), out_templ);
1247 			break;
1248 	}
1249 }
1250 
1251 void outputTemplateForTarget(ref Terminal t, string target = __MODULE__)
1252 {
1253 	import std.array:split;
1254 	///If it is the default, the target will be "targets.wasm", so, split and get the last.
1255 	string buildTarget = getBuildTarget(target.split(".")[$-1]);
1256 	t.writeln("Regenerating buildscript for target ", buildTarget);
1257 	outputTemplate(t, buildTarget);
1258 }
1259 
1260 bool isIpAddress(string ip)
1261 {
1262 	import std.ascii;
1263 	import std.conv:to;
1264 	import std.string;
1265 
1266     string[] parts = split(ip, ".");
1267     if (parts.length != 4) return false;
1268 
1269     foreach (part; parts) {
1270         if (part.length == 0 || part.length > 3)
1271 			 return false;
1272 		foreach(v; part)
1273 			if(!isDigit(v))
1274 				return false;
1275         int value = to!int(part);
1276         if (value < 0 || value > 255 || (part[0] == '0' && part.length > 1)) return false;
1277     }
1278 
1279     return true;
1280 }
1281 
1282 void requireConfiguration(
1283 	string cfgRequired,
1284 	string purpose,
1285 	ref Terminal t,
1286 	ref RealTimeConsoleInput input,
1287 	bool function(ref string inOutData) validation = null,
1288 	string validationFailMsg = "Validation Failure"
1289 )
1290 {
1291 	import std.string:strip;
1292 
1293 	while(true)
1294 	{
1295 		string res = cfgRequired in configs ? configs[cfgRequired].str : null;
1296 		if(res.length != 0)
1297 		{
1298 			if(validation && validation(res))
1299 				break;
1300 			t.writelnError(validationFailMsg);
1301 		}
1302 		res = t.getline("Config '"~cfgRequired~"' is required for "~ purpose~ ". \n\tWrite here: ").strip();
1303 
1304 		if(validation && validation(res))
1305 		{
1306 			configs[cfgRequired] = res;
1307 			updateConfigFile();
1308 			break;
1309 		}
1310 
1311 	}
1312 }
1313 
1314 /** 
1315 * 
1316 * Params:
1317 *   original = The original path where the link will redirect
1318 *   link = The path where the link will be created
1319 */
1320 void symlink(string original, string link)
1321 {
1322 	version(Posix){
1323 		std.file.symlink(original, link);
1324 	}
1325 	version(Windows)
1326 	{
1327 		import core.sys.windows.w32api:_WIN32_WINNT;
1328 		static if(_WIN32_WINNT >= 0x600) //WindowsVista or later
1329 		{
1330 			import core.sys.windows.winbase;
1331 			import core.sys.windows.windef:DWORD, MAX_PATH, LPWSTR;
1332 			import std.utf:toUTF16z;
1333 			import std.file:FileException;
1334 
1335 			DWORD typeFlag = 0; //File
1336 			if(std.file.isDir(original))
1337 				typeFlag = SYMBOLIC_LINK_FLAG_DIRECTORY;
1338 			typeFlag|= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
1339 
1340 			if(link.length > MAX_PATH) link = `\\?\`~link;
1341 			if(original.length > MAX_PATH) original = `\\?\`~original;
1342 
1343 			if(!CreateSymbolicLinkW(link.toUTF16z, original.toUTF16z, typeFlag))
1344 			{
1345 				LPWSTR strBuffer;
1346 				DWORD length = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, null, GetLastError(),0, cast(LPWSTR)&strBuffer, 0, null);
1347 				wchar[] str = new wchar[length];
1348 				str[] = strBuffer[0..str.length];
1349 				LocalFree(strBuffer);
1350 				import std.conv;
1351 				throw new FileException(original, str.to!string);
1352 			}
1353 		}
1354 	}
1355 }
1356 
1357 
1358 /** 
1359  * May be used in future. Kept for reference.
1360  */
1361 private bool hasAdminRights()
1362 {
1363 	version(Windows)
1364 	{
1365 		///https://stackoverflow.com/questions/8046097/how-to-check-if-a-process-has-the-administrative-rights
1366 		import core.sys.windows.windows;
1367 		bool hasRights = false;
1368 		HANDLE hToken = NULL;
1369 		if( OpenProcessToken( GetCurrentProcess( ),TOKEN_QUERY,&hToken ) ) {
1370 			TOKEN_ELEVATION Elevation;
1371 			DWORD cbSize = TOKEN_ELEVATION.sizeof;
1372 			if(GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevation, &Elevation, Elevation.sizeof, &cbSize))
1373 				hasRights = Elevation.TokenIsElevated == 1;
1374 		}
1375 		if(hToken) CloseHandle(hToken);
1376 		return hasRights;
1377 	}
1378 	else return false;
1379 }
1380 
1381 
1382 static this()
1383 {
1384 	configs = std.file.exists(getConfigPath) ? Config(parseJSON(std.file.readText(getConfigPath))) : Config(parseJSON("{}"));
1385 	try engineConfig = std.file.exists(getEngineConfigPath) ? parseJSON(std.file.readText(getEngineConfigPath)) : parseJSON("{}");
1386 	catch(Exception e) engineConfig = parseJSON("{}");
1387 }